/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "super_block_writer.h"

#include <cmath>
#include <string>
#include <sys/types.h>
#include <cinttypes>

#ifdef HAVE_UUID_UUID_H
#include <uuid.h>
#endif
#ifndef HAVE_LIBUUID
#define uuid_parse(a, b) -1
#define uuid_generate(a)
#endif

#include "securec.h"
#include "hmfs_utils.h"
#include "mkfs_format.h"
#include "device_manager.h"
#include "hmfs_define.h"
#include "hmfs_io.h"
#include "hmfs_common.h"
#include "hmfs_zoned.h"
#include "hmfs_encoding.h"

namespace OHOS {
namespace Hmfs {

namespace {
const std::vector<std::string> defaultColdExtList = {
    /* common prefix */
    "mp", // Covers mp3, mp4, mpeg, mpg
    "wm", // Covers wma, wmb, wmv
    "og", // Covers oga, ogg, ogm, ogv
    "jp", // Covers jpg, jpeg, jp2

    /* video */
    "avi",
    "m4v",
    "m4p",
    "mkv",
    "mov",
    "webm",

    /* audio */
    "wav",
    "m4a",
    "3gp",
    "opus",
    "flac",

    /* image */
    "gif",
    "png",
    "svg",
    "webp",

    /* archives */
    "jar",
    "deb",
    "iso",
    "gz",
    "xz",
    "zst",

    /* others */
    "pdf",
    "pyc", // Python bytecode
    "ttc",
    "ttf",
    "exe",

    "apk",
    "cnt", // Image alias
    "exo", // YouTube
    "odex", // Android RunTime
    "vdex", // Android RunTime
    "so",
};
const std::vector<std::string> defaultHotExtList = {
    "db",
    "vmdk", // VMware or VirtualBox
    "vdi", // VirtualBox
    "qcow2", // QEMU
};
}

SuperBlockWriter::SuperBlockWriter(HmfsMkfs *mkfs) : mkfs_(mkfs)
{

}

int32_t SuperBlockWriter::Prepare()
{
    superBlock_ = std::make_unique<SuperBlockOnDisk>();
    if (superBlock_ == nullptr) {
        return -1;
    }
    memset_s(superBlock_.get(), sizeof(SuperBlockOnDisk), 0, sizeof(SuperBlockOnDisk));

    if (FillSuperBlockData()) {
        return -1;
    }

    if (UpdateHmfsData()) {
        return -1;
    }

    return 0;
}

bool NearlyEqual(double a, double b, double epsilon = 0.000001) {
    return std::fabs(a - b) < epsilon;
}

int32_t SuperBlockWriter::UpdateHmfsData()
{
    CmdConfig& cfgPara = mkfs_->cfgPara_;
    HmfsData& hmfsData = mkfs_->hmfsData_;
    struct SuperBlockData* superBlock = &superBlock_->superBlock;

    if (cfgPara.features & HMFS_FEATURE_RO) {
        cfgPara.overProvision = 0;
        hmfsData.reservedSegments = 0;
    } else {
        uint32_t usableSegs = HmfsZoned::GetInstance().HmfsGetUsableSegments(superBlock);
        if (NearlyEqual(cfgPara.overProvision, 0.0)) {
            cfgPara.overProvision = HmfsCommon::GetInstance().GetBestOverProvision(superBlock);
        }
        if (NearlyEqual(cfgPara.overProvision, 0.0)) {
            HMFS_ERROR("device size is not sufficient for HMFS volume");
            return -1;
        }

        hmfsData.reservedSegments = (2 * (100 / cfgPara.overProvision + 1) + CURSEG_TYPE_MAX) *
            AlignUpCount(usableSegs, hmfsData.sectionCount);
    }

    if ((!(cfgPara.features & HMFS_FEATURE_RO) && cfgPara.overProvision == 0) ||
        // (hmfsData.devices[0].sectorCount * hmfsData.sectorSize < hmfsData.zoneAlignStartOffset) ||
        ((hmfsData.segmentCountInMain - CURSEG_TYPE_MAX) < hmfsData.reservedSegments)) {
        HMFS_ERROR("device size is not sufficient for HMFS volume");
        HMFS_DEBUG("cfgPara.overProvision = %f", cfgPara.overProvision);
        HMFS_DEBUG("hmfsData.segmentCount = %u", hmfsData.segmentCount);
        HMFS_DEBUG("hmfsData.segmentCountInMain = %u, CURSEG_TYPE_MAX = %u, hmfsData.reservedSegments = %u", 
            hmfsData.segmentCountInMain, CURSEG_TYPE_MAX, hmfsData.reservedSegments);
        return -1;
    }

    return 0;
}

int32_t SuperBlockWriter::FillSuperBlockData()
{
    CmdConfig& cfgPara = mkfs_->cfgPara_;
    HmfsData& hmfsData = mkfs_->hmfsData_;
    struct SuperBlockData* superBlock = &superBlock_->superBlock;

    SetLeValue(superBlock->magicNo, HMFS_MAGIC_NUMBER);
    SetLeValue(superBlock->majorVersion, HMFS_MAJOR_VERSION);
    SetLeValue(superBlock->minorVersion, HMFS_MINOR_VERSION);

    uint32_t log_sectorsize = HmfsCommon::GetInstance().LogBase2(hmfsData.sectorSize);
    uint32_t log_sectors_per_block = HmfsCommon::GetInstance().LogBase2(hmfsData.sectorsPerBlk);
    uint32_t log_blocksize = log_sectorsize + log_sectors_per_block;
    uint32_t log_blks_per_seg = HmfsCommon::GetInstance().LogBase2(BLOCKS_PER_SEGMENT);
    SetLeValue(superBlock->logSectorSize, log_sectorsize);
    SetLeValue(superBlock->logSectorsPerBlk, log_sectors_per_block);
    SetLeValue(superBlock->logBlockSize, log_blocksize);
    SetLeValue(superBlock->logBlksPerSeg, log_blks_per_seg);
    SetLeValue(superBlock->segsPerSection, cfgPara.segsPerSection);
    SetLeValue(superBlock->sectionsPerZone, cfgPara.sectionsPerZone);
    SetLeValue(superBlock->blockCount, hmfsData.sectorCount >> log_sectors_per_block);
    SetLeValue(superBlock->sectionCount, hmfsData.sectionCount);
    SetLeValue(superBlock->segmentCount, hmfsData.zoneAlignedSegCount);
    SetLeValue(superBlock->segmentCountInCP, hmfsData.segmentCountInCP);
    SetLeValue(superBlock->segmentCountInSIT, hmfsData.segmentCountInSIT);
    SetLeValue(superBlock->segmentCountInNAT, hmfsData.segmentCountInNAT);
    SetLeValue(superBlock->segmentCountInSSA, hmfsData.segmentCountInSSA);
    SetLeValue(superBlock->segmentCountInMain, hmfsData.segmentCountInMain);
    SetLeValue(superBlock->segment0BlkId, hmfsData.segment0BlkId);
    superBlock->cpBlkId = superBlock->segment0BlkId;
    SetLeValue(superBlock->sitBlkId, hmfsData.sitStartBlkId);
    SetLeValue(superBlock->natBlkId, hmfsData.natStartBlkId);
    SetLeValue(superBlock->ssaBlkId, hmfsData.natStartBlkId + hmfsData.segmentCountInNAT * BLOCKS_PER_SEGMENT);
    SetLeValue(superBlock->mainBlkId, hmfsData.mainStartBlkId);
    SetLeValue(superBlock->rootInodeId, hmfsData.rootInode);
    SetLeValue(superBlock->nodeInodeId, hmfsData.nodeInode);
    SetLeValue(superBlock->metaInodeId, hmfsData.metaInode);

    for (int32_t qtype = 0; qtype < MAXQUOTAS; qtype++) {
        if (((1 << qtype) & cfgPara.quotaBits)) {
            SetLeValue(superBlock->qfInodeId[qtype], hmfsData.nextFreeInodeId++);
            HMFS_INFO("add quota type = %u => %u\n", qtype, hmfsData.nextFreeInodeId - 1);
        }
    }

    if (cfgPara.features & HMFS_FEATURE_LOST_FOUND) {
        hmfsData.lpfIno = hmfsData.nextFreeInodeId++;
    }

    SetLeValue(superBlock->cpPayload, hmfsData.cpPayload);
    memcpy_s(superBlock->version, sizeof(superBlock->version), hmfsData.version, VERSION_TOTAL_LEN);
    memcpy_s(superBlock->initVersion, sizeof(superBlock->initVersion), hmfsData.version, VERSION_TOTAL_LEN);
    SetLeValue(superBlock->features, cfgPara.features);

    uint32_t deviceCount = DeviceManager::GetInstance().GetDeviceCount();
    if (deviceCount > 1) {
        for (uint32_t id = 0; id < deviceCount; id++) {
            DeviceInfo* deviceInfo = DeviceManager::GetInstance().GetDeviceInfo(id);
            if (deviceInfo == nullptr) {
                HMFS_ERROR("failed to get device info, id %u", id);
                return -1;
            }
            strcpy_s(superBlock->devices[id].path, DEVICE_PATH_MAX_LEN, deviceInfo->path.c_str());
            superBlock->devices[id].segmentCount = deviceInfo->segmentCount;
        }
    }

    if (cfgPara.uuid.empty()) {
        uuid_generate(superBlock->uuid);
    } else {
        if (uuid_parse(cfgPara.uuid.c_str(), superBlock->uuid)) {
            HMFS_ERROR("Supplied string is not a valid UUID.");
            return -1;
        }
    }

    if (cfgPara.features & HMFS_FEATURE_INODE_CHKSUM) {
        hmfsData.chksumSeed = HmfsCommon::GetInstance().HmfsCalCrc32(~0, superBlock->uuid, sizeof(superBlock->uuid));
    }

    Utf8ToUtf16(superBlock->volumeName, cfgPara.volume.c_str(), VOLUME_NAME_MAX_LEN, cfgPara.volume.length()); // TODO: check

    FillExtList();

    if (cfgPara.features & HMFS_FEATURE_CASEFOLD) {
        SetLeValue(superBlock->encoding, cfgPara.sEncoding);
        SetLeValue(superBlock->encodingFlags, cfgPara.sEncodingFlags);
    }

    return 0;
}

void SuperBlockWriter::FillExtList()
{
    CmdConfig& cfgPara = mkfs_->cfgPara_;

    struct SuperBlockData* superBlock = &superBlock_->superBlock;
    std::vector<std::pair<std::vector<std::string>, std::vector<std::string>>> extList = {
        std::make_pair(defaultColdExtList, cfgPara.coldExtList),
        std::make_pair(defaultHotExtList, cfgPara.hotExtList),
    };

    uint32_t index = 0;
    uint32_t coldExtCount = 0;
    for (size_t i = 0; i < extList.size(); i++) {
        for (auto &ext : extList[i].first) {
            if (index >= EXTENSION_COUNT_MAX) {
                break;
            }
            strcpy_s(superBlock->extensionList[index++], EXTENSION_LEN_MAX, ext.c_str());
        }

        for (auto &ext : extList[i].second) {
            if (index >= EXTENSION_COUNT_MAX) {
                break;
            }

            if (!IsExtensionDuplicate(ext)) {
                strcpy_s(superBlock->extensionList[index++], EXTENSION_LEN_MAX, ext.c_str());
            }
        }

        if (i == 0) {
            coldExtCount = index;
        }
    }

    uint32_t hotExtCount = index - coldExtCount;
    SetLeValue(superBlock->hotExtensionCount, hotExtCount);
    SetLeValue(superBlock->coldExtensionCount, coldExtCount);
}

bool SuperBlockWriter::IsExtensionDuplicate(std::string& ext)
{
    struct SuperBlockData* superBlock = &superBlock_->superBlock;
    for (uint32_t i = 0; i < EXTENSION_COUNT_MAX; i++) {
        if (strcmp(superBlock->extensionList[i], ext.c_str()) == 0) {
            return true;
        }
    }
    return false;
}

int32_t SuperBlockWriter::ClacCheckSum()
{
    CmdConfig& cfgPara = mkfs_->cfgPara_;
    struct SuperBlockData* superBlock = &superBlock_->superBlock;

    if (cfgPara.features & HMFS_FEATURE_SB_CHKSUM) {
        SetLeValue(superBlock->checksumOffset, SB_CHKSUM_OFFSET);
        SetLeValue(superBlock->checksum, HmfsCommon::GetInstance().HmfsCalCrc32(HMFS_MAGIC_NUMBER, superBlock, SB_CHKSUM_OFFSET));
        HMFS_INFO("super block checksum is set: offset (%u), crc (0x%x)",
            GetLeValue(superBlock->checksumOffset), GetLeValue(superBlock->checksum));
    }

    return 0;
}

int32_t SuperBlockWriter::Write()
{
    ClacCheckSum();

    for (uint64_t i = 0; i < HMFS_SUPER_BLOCK_COUNT; i++) {
        if (HmfsIo::GetInstance().HmfsIo::GetInstance().DevWriteBlock(superBlock_.get(), i)) {
            HMFS_ERROR("failed to write super block [%" PRIu64 "] on disk", i);
            return -1;
        }
    }
    return 0;
}

} // namespace Hmfs
} // namespace OHOS
